﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mindfor.Web.Data
{
	/// <summary>
	/// Provides extensions functions to query pages.
	/// </summary>
	public static class QueryPageExtensions
	{
		/// <summary>
		/// Returns root pages.
		/// </summary>
		/// <returns>Root pages.</returns>
		public static IQueryable<Page> GetRootPages(this IQueryable<Page> pages)
		{
			return pages.Where(p => p.Parent == null);
		}

		/// <summary>
		/// Returns list of pages sorted in tree-order.
		/// </summary>
		public static List<Page> GetTreePages(this IEnumerable<Page> pages)
		{
			return GetTreePages(pages, null);
		}

		/// <summary>
		/// Returns list of pages sorted in tree-order.
		/// </summary>
		/// <param name="excludePage">Page to exclude from list.</param>
		public static List<Page> GetTreePages(this IEnumerable<Page> pages, Page excludePage)
		{
			List<Page> allPages = pages.ToList();
			List<Page> res = new List<Page>();
			foreach (Page p in allPages.Where(r => r.Parent == null).OrderBy(r => r.Sort))
			{
				if (p != excludePage)
				{
					res.Add(p);
					if (p.ChildPages.Count > 0)
						res.AddRange(GetTreePagesInner(p, excludePage));
				}
			}
			return res;
		}

		private static List<Page> GetTreePagesInner(Page parent, Page excludePage)
		{
			List<Page> res = new List<Page>();
			foreach (Page p in parent.ChildPages.OrderBy(r => r.Sort))
			{
				if (p != excludePage)
				{
					res.Add(p);
					if (p.ChildPages.Count > 0)
						res.AddRange(GetTreePagesInner(p, excludePage));
				}
			}
			return res;
		}

		/// <summary>
		/// Returns page by path.
		/// </summary>
		/// <param name="path">Page path like "Page1/Page2/Page3".</param>
		/// <returns>Page or <c>Null</c> if not found.</returns>
		public static Page GetPage(this IQueryable<Page> pages, string path)
		{
			// check path
			if (path == null)
				return null;

			// split path into page names
			path = path.Trim(' ', '/');
			
			// find root page
			string[] parts = path.Split('/');
			Page page = pages.FirstOrDefault(p => p.Parent == null && p.Name.ToLower() == parts[0].ToLower());
			if (page == null)
				return null;

			for (int i=1; i<parts.Length; i++)
			{
				// find next level page
				string name = parts[i].ToLower();
				page = page.ChildPages.FirstOrDefault(p => p.Name.ToLower() == name);

				// page not found
				if (page == null)
					return null;
			}

			// if came here -> page is found
			return page;
		}

		/// <summary>
		/// Moves page up in current level pages list.
		/// </summary>
		/// <param name="page">Page to move up.</param>
		/// <returns>Changed pages.</returns>
		public static Page[] MovePageUp(this IQueryable<Page> pages, Page page)
		{
			// normalize sort
			List<Page> levelPages = page.Parent != null ?
				pages.Where(p => p.Parent.Id == page.Parent.Id).ToList() :
				pages.Where(p => p.Parent == null).ToList();
			HashSet<Page> changed = NormalizeSort(levelPages);

			// find another page
			if (page.Sort > 0)
			{
				int sort = page.Sort;
				int sortPrev = sort - 1;

				Page pagePrev = levelPages.First(p => p.Sort == sortPrev);
				pagePrev.Sort = sort;
				page.Sort = sortPrev;

				changed.Add(page);
				changed.Add(pagePrev);
			}

			return changed.ToArray();
		}

		/// <summary>
		/// Moves page down in current level pages list.
		/// </summary>
		/// <param name="page">Page to move down.</param>
		/// <returns>Changed pages.</returns>
		public static Page[] MovePageDown(this IQueryable<Page> pages, Page page)
		{
			// normalize sort
			List<Page> levelPages = page.Parent != null ?
				pages.Where(p => p.Parent.Id == page.Parent.Id).ToList() :
				pages.Where(p => p.Parent == null).ToList();
			HashSet<Page> changed = NormalizeSort(levelPages);
			int maxSort = levelPages.Max(p => p.Sort);

			// find another page
			if (page.Sort < maxSort)
			{
				int sort = page.Sort;
				int sortNext = sort + 1;

				Page pageNext = levelPages.First(p => p.Sort == sortNext);
				pageNext.Sort = sort;
				page.Sort = sortNext;

				changed.Add(page);
				changed.Add(pageNext);
			}

			return changed.ToArray();
		}

		private static HashSet<Page> NormalizeSort(IEnumerable<Page> pages)
		{
			int i = 0;
			HashSet<Page> changed = new HashSet<Page>();
			foreach (Page page in pages.OrderBy(p => p.Sort))
			{
				if (page.Sort != i)
				{
					page.Sort = i;
					changed.Add(page);
				}
				i++;
			}
			return changed;
		}
	}
}
